﻿using System;
using System.IO;
using MetaMapics.Pbf;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTest_MetaMapics.Pbf
{
  [TestClass]
  public class UnitTest_PbfWriter_PbfReader
  {
    private class ClassA : IPbfStreamable, IEquatable<ClassA>
    {
      public string A { get; set; }
      public int B { get; set; }

      public void WriteToPbf(PbfWriter writer)
      {
        if (A != null)
          writer.Write(1, A);
        writer.Write(2, B);
      }

      public void ReadFromPbf(PbfReader reader)
      {
        foreach (int fieldNum in reader.Fields())
          switch (fieldNum)
          {
            case 1:
              A = reader.ReadString();
              break;
            case 2:
              B = reader.ReadInt();
              break;
            default:
              reader.IgnoreField();
              break;
          }
      }

      public bool Equals(ClassA other)
      {
        return
          (A==null && other.A==null || A == other.A) &&
          B == other.B;
      }
    }

    [TestMethod]
    public void Test_IPbfStreamable()
    {
      var a = new ClassA();
      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        a.WriteToPbf(writer);
        stream.Seek(0, SeekOrigin.Begin);
        ClassA a1 = new ClassA();
        a1.ReadFromPbf(reader);
        Assert.IsTrue(a.Equals(a1));
      }
    }

    [TestMethod]
    public void TestStandardTypes()
    {
      int f1 = 12345;
      long f2 = 123456789101112;
      float f3 = 123.456F;
      double f4 = 123.456F;
      bool f5 = true;
      string f6 = "abcdef£££";
      uint f7 = uint.MaxValue;
      ulong f8 = ulong.MaxValue;
      byte[] f9 = { 1, 2, 3, 255 };

      int r1 = 0;
      long r2 = 0;
      float r3 = 0;
      double r4 = 0;
      bool r5 = false;
      string r6 = null;
      uint r7 = 0;
      ulong r8 = 0;
      byte[] r9 = null;

      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        writer.Write(1, f1);
        writer.Write(2, f2);
        writer.Write(3, f3);
        writer.Write(4, f4);
        writer.Write(5, f5);
        writer.Write(6, f6);
        writer.Write(7, f7);
        writer.Write(8, f8);
        writer.Write(9, f9);

        stream.Seek(0, SeekOrigin.Begin);

        foreach (int fieldNum in reader.Fields())
          switch (fieldNum)
          {
            case 1:
              r1 = reader.ReadInt();
              break;
            case 2:
              r2 = reader.ReadLong();
              break;
            case 3:
              r3 = reader.ReadFloat();
              break;
            case 4:
              r4 = reader.ReadDouble();
              break;
            case 5:
              r5 = reader.ReadBool();
              break;
            case 6:
              r6 = reader.ReadString();
              break;
            case 7:
              r7 = reader.ReadUint();
              break;
            case 8:
              r8 = reader.ReadUlong();
              break;
            case 9:
              r9 = reader.ReadBytes();
              break;
            default:
              throw new InvalidDataException();
          }
        Assert.AreEqual(f1, r1);
        Assert.AreEqual(f2, r2);
        Assert.AreEqual(f3, r3);
        Assert.AreEqual(f4, r4);
        Assert.AreEqual(f5, r5);
        Assert.AreEqual(f6, r6);
        Assert.AreEqual(f7, r7);
        Assert.AreEqual(f8, r8);

        bool match = f9.Length == r9.Length;
        for (int i = 0; i < f9.Length; i++)
          if (f9[i] != r9[i])
            match = false;
        Assert.IsTrue(match);
      }
    }

    [TestMethod]
    public void TestZigZag()
    {
      int[] f1 = { 0, -1, 1, 1234, -2, -1234, int.MaxValue, int.MinValue };
      long[] f2 = { 0, -1, 1, 1234, -2, -1234, long.MaxValue, long.MinValue };

      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        for (uint i = 0; i < f1.Length; i++)
          writer.Write(i + 1, f1[i], PbfFieldType.Sint32); 
        for (uint i = 0; i < f2.Length; i++)
          writer.Write((uint)(f1.Length + i + 1), f2[i], PbfFieldType.Sint64);

        stream.Seek(0, SeekOrigin.Begin);

        bool success = true;
        foreach (int fieldNum in reader.Fields())
          if (fieldNum-1 < f1.Length)
          {
            if (f1[fieldNum-1] != reader.ReadInt(PbfFieldType.Sint32))
              success = false;
          }
          else if (fieldNum-1 < f1.Length + f2.Length)
          {
            if (f2[fieldNum - f1.Length - 1] != reader.ReadLong(PbfFieldType.Sint64))
             success = false;
          }
      Assert.IsTrue(success);
      }
    }

    [TestMethod]
    public void TestFixed()
    {
      int[] f1 = { 0, -1, 1, 1234, -2, -1234, int.MaxValue, int.MinValue };
      long[] f2 = { 0, -1, 1, 1234, -2, -1234, long.MaxValue, long.MinValue };
      uint[] f3 = { 0, 1, 1234, uint.MaxValue, uint.MinValue };
      ulong[] f4 = { 0, 1, 1234, ulong.MaxValue, ulong.MinValue };

      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        for (uint i = 0; i < f1.Length; i++)
          writer.Write(i + 1, f1[i], PbfFieldType.Sfixed32);
        stream.Seek(0, SeekOrigin.Begin);
        bool success = true;
        foreach (int fieldNum in reader.Fields())
          if (fieldNum - 1 < f1.Length)
          {
            if (f1[fieldNum - 1] != reader.ReadInt(PbfFieldType.Sfixed32))
              success = false;
          }
          else
            reader.IgnoreField();
        Assert.IsTrue(success);
      }
      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        stream.Seek(0, SeekOrigin.Begin);
        for (uint i = 0; i < f2.Length; i++)
          writer.Write(i + 1, f2[i], PbfFieldType.Sfixed64);
        stream.Seek(0, SeekOrigin.Begin);
        bool success = true;
        foreach (int fieldNum in reader.Fields())
          if (fieldNum - 1 < f2.Length)
          {
            if (f2[fieldNum - 1] != reader.ReadLong(PbfFieldType.Sfixed64))
              success = false;
          }
          else
            reader.IgnoreField();
        Assert.IsTrue(success);
      }
      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        stream.Seek(0, SeekOrigin.Begin);
        for (uint i = 0; i < f3.Length; i++)
          writer.Write(i + 1, f3[i], PbfFieldType.Fixed32);
        stream.Seek(0, SeekOrigin.Begin);
        bool success = true;
        foreach (int fieldNum in reader.Fields())
          if (fieldNum - 1 < f3.Length)
          {
            if (f3[fieldNum - 1] != reader.ReadUint(PbfFieldType.Fixed32))
              success = false;
          }
          else
            reader.IgnoreField();
        Assert.IsTrue(success);
      }
      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        stream.Seek(0, SeekOrigin.Begin);
        for (uint i = 0; i < f4.Length; i++)
          writer.Write(i + 1, f4[i], PbfFieldType.Fixed64);
        stream.Seek(0, SeekOrigin.Begin);
        bool success = true;
        foreach (int fieldNum in reader.Fields())
          if (fieldNum - 1 < f4.Length)
          {
            if (f4[fieldNum - 1] != (ulong)reader.ReadUlong(PbfFieldType.Fixed64))
              success = false;
          }
          else
            reader.IgnoreField();
        Assert.IsTrue(success);
      }
    }

    [TestMethod]
    public void TestPacked()
    {
      double[] f1 = { 0, -1, 1, 1234, -2, -1234, 0.1, -0.1, double.MaxValue, double.MinValue, double.Epsilon };
      float[] f2 = { 0, -1, 1, 1234, -2, -1234, 0.1f, -0.1f, float.MaxValue, float.MinValue, float.Epsilon };
      int[] f3 = { 0, -1, 1, 1234, -2, -1234, int.MaxValue, int.MinValue };
      bool[] f4 = { true, false, false, true };
      long[] f5 = { 0, -1, 1, 1234, -2, -1234, long.MaxValue, long.MinValue };
      uint[] f6 = { 0, 1, 1234, uint.MaxValue, uint.MinValue };
      ulong[] f7 = { 0, 1, 1234, ulong.MaxValue, ulong.MinValue };

      double[] r1 = null;
      float[] r2 = null;
      int[] r3 = null;
      bool[] r4 = null;
      long[] r5 = null;
      uint[] r6 = null;
      ulong[] r7 = null;


      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        writer.WritePacked(1, f1);
        writer.WritePacked(2, f2);
        writer.WritePacked(3, f3);
        writer.WritePacked(4, f4);
        writer.WritePacked(5, f5);
        writer.WritePacked(6, f6);
        writer.WritePacked(7, f7);

        stream.Seek(0, SeekOrigin.Begin);

        foreach (int fieldNum in reader.Fields())
          switch (fieldNum)
          {
            case 1:
              r1 = reader.ReadPackedDouble();
              break;
            case 2:
              r2 = reader.ReadPackedFloat();
              break;
            case 3:
              r3 = reader.ReadPackedInt();
              break;
            case 4:
              r4 = reader.ReadPackedBool();
              break;
            case 5:
              r5 = reader.ReadPackedLong();
              break;
            case 6:
              r6 = reader.ReadPackedUint();
              break;
            case 7:
              r7 = reader.ReadPackedUlong();
              break;
            default:
              throw new InvalidDataException();
          }

        bool match1 = f1.Length == r1.Length;
        for (int i = 0; i < f1.Length; i++)
          if (f1[i] != r1[i])
            match1 = false;

        bool match2 = f2.Length == r2.Length;
        for (int i = 0; i < f2.Length; i++)
          if (f2[i] != r2[i])
            match2 = false;

        bool match3 = f3.Length == r3.Length;
        for (int i = 0; i < f3.Length; i++)
          if (f3[i] != r3[i])
            match3 = false;

        bool match4 = f4.Length == r4.Length;
        for (int i = 0; i < f4.Length; i++)
          if (f4[i] != r4[i])
            match4 = false;

          bool match5 = f5.Length == r5.Length;
          for (int i = 0; i < f5.Length; i++)
            if (f5[i] != r5[i])
              match5 = false;

        bool match6 = f6.Length == r6.Length;
        for (int i = 0; i < f6.Length; i++)
          if (f6[i] != r6[i])
            match6 = false;

        bool match7 = f7.Length == r7.Length;
        for (int i = 0; i < f7.Length; i++)
          if (f7[i] != r7[i])
            match7 = false;

        Assert.IsTrue(match1);
        Assert.IsTrue(match2);
        Assert.IsTrue(match3);
        Assert.IsTrue(match4);
        Assert.IsTrue(match5);
        Assert.IsTrue(match6);
        Assert.IsTrue(match7);
      }
    }

    [TestMethod]
    public void TestPackedZigZag()
    {
      int[] f1 = { 0, -1, 1, 1234, -2, -1234, int.MaxValue, int.MinValue };
      long[] f2 = { 0, -1, 1, 1234, -2, -1234, long.MaxValue, long.MinValue };

      int[] r1 = null;
      long[] r2 = null;

      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        writer.WritePacked(1, f1, PbfFieldType.Sint32);
        writer.WritePacked(2, f2, PbfFieldType.Sint64);

        stream.Seek(0, SeekOrigin.Begin);

        foreach (int fieldNum in reader.Fields())
          switch (fieldNum)
          {
            case 1:
              r1 = reader.ReadPackedInt(PbfFieldType.Sint32);
              break;
            case 2:
              r2 = reader.ReadPackedLong(PbfFieldType.Sint64);
              break;
            default:
              throw new InvalidDataException();
          }

        bool match1 = f1.Length == r1.Length;
        for (int i = 0; i < f1.Length; i++)
          if (f1[i] != r1[i])
            match1 = false;

        bool match2 = f2.Length == r2.Length;
        for (int i = 0; i < f2.Length; i++)
          if (f2[i] != r2[i])
            match2 = false;

        Assert.IsTrue(match1);
        Assert.IsTrue(match2);
      }
    }
   
    [TestMethod]
    public void TestPackedFixed()
    {
      int[] f1 = { 0, -1, 1, 1234, -2, -1234, int.MaxValue, int.MinValue };
      long[] f2 = { 0, -1, 1, 1234, -2, -1234, long.MaxValue, long.MinValue };
      uint[] f3 = { 0, 1, 1234, uint.MaxValue, uint.MinValue };
      ulong[] f4 = { 0, 1, 1234, ulong.MaxValue, ulong.MinValue };

      int[] r1 = null;
      long[] r2 = null;
      uint[] r3 = null;
      ulong[] r4 = null;

      using (var stream = new MemoryStream())
      using (var writer = new PbfWriter(stream))
      using (var reader = new PbfReader(stream))
      {
        writer.WritePacked(1, f1, PbfFieldType.Sfixed32);
        writer.WritePacked(2, f2, PbfFieldType.Sfixed64);
        writer.WritePacked(3, f3, PbfFieldType.Fixed32);
        writer.WritePacked(4, f4, PbfFieldType.Fixed64);

        stream.Seek(0, SeekOrigin.Begin);

        foreach (int fieldNum in reader.Fields())
          switch (fieldNum)
          {
            case 1:
              r1 = reader.ReadPackedInt(PbfFieldType.Sfixed32);
              break;
            case 2:
              r2 = reader.ReadPackedLong(PbfFieldType.Sfixed64);
              break;
            case 3:
              r3 = reader.ReadPackedUint(PbfFieldType.Fixed32);
              break;
            case 4:
              r4 = reader.ReadPackedUlong(PbfFieldType.Fixed64);
              break;
            default:
              throw new InvalidDataException();
          }

        bool match1 = f1.Length == r1.Length;
        for (int i = 0; i < f1.Length; i++)
          if (f1[i] != r1[i])
            match1 = false;

        bool match2 = f2.Length == r2.Length;
        for (int i = 0; i < f2.Length; i++)
          if (f2[i] != r2[i])
            match2 = false;

        bool match3 = f3.Length == r3.Length;
        for (int i = 0; i < f3.Length; i++)
          if (f3[i] != r3[i])
            match3 = false;

        bool match4 = f4.Length == r4.Length;
        for (int i = 0; i < f4.Length; i++)
          if (f4[i] != r4[i])
            match4 = false;

        Assert.IsTrue(match1);
        Assert.IsTrue(match2);
        Assert.IsTrue(match3);
        Assert.IsTrue(match4);
      }
    }

    [TestMethod]
    public void TestExceptions()
    {
      AssertEx.Throws<ArgumentException>(()=>"".Substring(20, 20));
    }
  }
}
